package sjtu.FaceRecognition;

import java.util.List;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.util.Log;
import android.view.OrientationEventListener;
import android.view.SurfaceView;

public class CameraUtil {
	private static final String TAG = "CameraUtil";
	private static Camera frontCamera = null;
	private static int facing;
		
	//preview pic width, assigned by initCameraSettings()
	private static int previewWidth = 0;
	
	public static int getPreviewWidth() {
		return previewWidth;
	}
		
	//preview pic height, assigned by initCameraSettings()
	private static int previewHeight = 0;

	public static int getPreviewHeight() {
		return previewHeight;
	}
		
	//preview pic size, assigned by initCameraSettings()
	private static int previewBufferSize = 0;
	
	public static int getPreviewBufferSize() {
		return previewBufferSize;
	}
	
	private static int cameraOrientation = 0;
	
	//the angle of pic need to rotate
	private static int picRotation = 0;
	
	public static int getPicRotation() {
		return picRotation;
	}
	
	//phone orientation
	private static int phoneOrientation;
	
	public static int getPhoneOrientation() {
		return phoneOrientation;
	}
	
	/**
	 * 
	 * @param deviceOrientation, the orientation from sensor
	 * this method calculate the angle the pic should be rotated, 
	 * using the help of device orientation.
	 * And this code is from SDK.
	 */
	public static void setRotation(int deviceOrientation) {
		phoneOrientation = deviceOrientation;
		if (phoneOrientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
			picRotation = -1;
			return ;
		}
		phoneOrientation = (phoneOrientation + 45) / 90 * 90;
		if (facing == CameraInfo.CAMERA_FACING_FRONT) {
			picRotation = (cameraOrientation - phoneOrientation + 360) % 360; 
		} else {
			picRotation = (cameraOrientation + phoneOrientation) % 360;
		}
	}

	public static int getOrientation() {
		return cameraOrientation;
	}

	private static float proximity = 0;
	
	public static void setProximity(float value) {
		if (CommonUtil.DEBUG_LOG) {
			Log.e(TAG, "proximity is " + value);
		}
		proximity = value;
	}
	
	public static float getProximity() {
		return proximity;
	}
	
	/**
	 * check if we are able to use the front-facing camera
	 * 
	 * @param context
	 * @return true if front-facing camera could be accessed; otherwise false
	 */
	private static boolean checkFrontCameraEnv(Context context) {
		// front-facing camera could only be accessed when sdk 2.3 or higher
		if (CommonUtil.CUR_SDK < 9)
			return false;
		PackageManager pm = context.getPackageManager();
		if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)
				&& pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)
				&& pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS)) {
			return true;
		}
		return false;
	}
	
	/**
	 * init the camera settings
	 * 
	 * @param camera
	 */
	private static void initCameraSettings(Camera camera) {
		if (isCameraReady(camera)) {
			Camera.Parameters p = camera.getParameters();
			p.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
			List<Size> sizes = p.getSupportedPreviewSizes();
			int best_width = CommonUtil.PIC_WIDTH, best_height = CommonUtil.PIC_HEIGHT;
			double minDiff = Double.MAX_VALUE;
			for (Camera.Size size : sizes) {
                if (Math.abs(size.height - CommonUtil.PIC_HEIGHT) < minDiff) {
                	best_width = size.width;
                	best_height = size.height;
                    minDiff = Math.abs(size.height - CommonUtil.PIC_HEIGHT);
                }
            }
			List<int[]> rates = p.getSupportedPreviewFpsRange();
			for (int i = 0;i < rates.size(); i++) {
				//Log.e(TAG, "rate " + i + " is " + rates.get(i)[0] + " " + rates.get(i)[1]);
			}
			List<Integer> formats = p.getSupportedPreviewFormats();
			for (int i = 0;i < formats.size(); i++) {
				//Log.e(TAG, "format " + i + " is " + formats.get(i));
			}
			int bitPerPixel = ImageFormat.getBitsPerPixel(p.getPreviewFormat());
			p.setPreviewSize(best_width, best_height);
			camera.setParameters(p);
			previewWidth = best_width;
			previewHeight = best_height;
			if (CommonUtil.DEBUG_LOG) {
				Log.e(TAG, "preview width is " + previewWidth + " height is " + previewHeight);
			}
			previewBufferSize = previewWidth * previewHeight * bitPerPixel / 8;
			if (CommonUtil.DEBUG_LOG) {
				Log.e(TAG, "preview format is" + p.getPreviewFormat());
			}
		}
	}

	/**
	 * get front-facing camera instance if exists, it seems that Camera.open(id)
	 * should be called in AsyncTask
	 * 
	 * @param context
	 * @return true if front camera could be opened, otherwise false
	 */
	public static boolean initFrontCameraInstance(Context context) {
		if (!checkFrontCameraEnv(context)) {
			return false;
		}
		int cameraCount = 0;
		Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
		cameraCount = Camera.getNumberOfCameras();
		for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
			Camera.getCameraInfo(camIdx, cameraInfo);
			if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
				try {
					frontCamera = Camera.open(camIdx);
				} catch (RuntimeException e) {
					return false;
				}
				cameraOrientation = cameraInfo.orientation;
				if (CommonUtil.DEBUG_LOG) {
					Log.e(TAG, "camera orientation is " + cameraOrientation);
				}
				facing = CameraInfo.CAMERA_FACING_FRONT;
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 
	 * @param camera
	 * @return if the camera instance has been created
	 */
	private static boolean isCameraReady(Camera camera) {
		return camera != null;
	}

	public static boolean isFrontCameraReady() {
		return isCameraReady(frontCamera);
	}
	
	/**
	 * set a preview to the camera
	 * 
	 * @param context
	 * @return the preview for the camera, note that this surfaceview should be
	 *         added to a layout before the preview taking effect
	 */
	private static SurfaceView initCameraPreview(Context context, Camera camera) {
		initCameraSettings(camera);
		return new CameraPreview(camera, context);
	}
	
	public static SurfaceView initFrontCameraPreview(Context context) {
		return initCameraPreview(context, frontCamera);
	}

	/**
	 * release the camera instance to ensure other apps could use it
	 */
	private static void releaseCamera(Camera camera) {
		if (isCameraReady(camera)) {
			camera.stopPreview();
			camera.setPreviewCallback(null);
			camera.release();
			camera = null;
		}
	}

	public static void releaseFrontCamera() {
		releaseCamera(frontCamera);
	}
	
	public static void setFrontCameraPreviewCallback(PreviewCallback pcb) {
		frontCamera.setPreviewCallback(pcb);
	}
	
	public static void addFrontCameraPreviewBuffer() {
		frontCamera.addCallbackBuffer(new byte[getPreviewBufferSize()]);
	}
	
	public static void frontCameraStartPreview() {
		frontCamera.startPreview();
	}
	
	public static void frontCameraStopPreview() {
		frontCamera.stopPreview();
	}
}
